#!/usr/bin/env python
# -*- coding: UTF-8 -*-

'''
节点类
负责记录和维护节点自身信息以及与这个节点相关的上下游连接，实现输出值与误差项的计算
'''

from numpy import *


def sigmoid(inX):
    '''
        sigmoid 函数
    '''
    return 1.0 / (1 + exp(-inX))


class Node(object):
    def __init__(self, layer_index, node_index):
        '''
        构造节点
        :param layer_index: 节点所属层的编号
        :param node_index:  节点的编号
        '''
        self.layer_index = layer_index
        self.node_index = node_index
        ## 下游节点的集合
        self.down_stream = []
        # 上游节点的集合
        self.up_stream = []
        # 当前节点的输出
        self.output = 0
        # 当前节点的误差δ
        '''
            当节点是输出节点时:δ = ( t - y) * y * (1-y)   t 为预测值，y 为真是输出值
            当节点是隐藏节点时:δ = a * (1- a) * ∑(δk * ωkj)  k 下层节点索引， ωki 为 隐含节点i到下层节点k的权值
        '''
        self.delta = 0

    def set_output(self, output):
        '''
        设置节点的输出值。如果节点属于输入层需要用到这个函数
        :param output:  输出值
        '''
        self.output = output

    def append_down_stream_connection(self, conn):
        '''
        添加一个到下游节点的连接
        :param conn: 连接
        '''
        self.down_stream.append(conn)

    def append_up_stream_connection(self, conn):
        '''
        添加一个到上游节点的连接
        :param conn:  连接
        '''
        self.up_stream.append(conn)

    def calc_output(self):
        '''
        根据计算公式计算节点的输出
        计算公式：o = sigmoid(ω * X)   ω为权值向量，X为该节点的输入向量
        :return:
        '''
        output = reduce(lambda ret, conn: ret + conn.up_stream_node.output * conn.weight, self.up_stream, 0)
        self.output = sigmoid(output)

    def calc_hidden_layer_delta(self):
        '''
        节点属于隐含层是，根据 δ = a * (1- a) * ∑(δk * ωkj) 计算 δ
        '''
        down_stream_delta = reduce(lambda ret, conn: ret + conn.down_stream_node.delta * conn.weight, self.down_stream,
                                   0.0)
        self.delta = self.output * (1 - self.output) * down_stream_delta

    def calc_output_layer_delta(self, label):
        '''
        节点属于输出层时，根据δ = ( t - y) * y * (1-y) 计算 δ
        '''
        self.delta = self.output * (1 - self.output) * (label - self.output)

    def __str__(self):
        '''
        打印节点信息
        '''
        node_str = '%u-%u: output: %f delta:%s' % (self.layer_index, self.node_index, self.output, self.delta)
        down_stream_str = reduce(lambda ret, conn: ret + "\n\t" + str(conn), self.down_stream, '')
        up_stream_str = reduce(lambda ret, conn: ret + "\n\t" + str(conn), self.up_stream, '')
        return node_str + "\n\t down_stream:" + down_stream_str + "\n\t up_stream:" + up_stream_str
